package com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.service;

import com.ideaaedi.commonspring.transaction.DefaultManualTransactionSupport;
import com.ideaaedi.zoo.diy.artifact.dynamic.datasource.datasource.DatasourceSwitcher;
import com.ideaaedi.zoo.diy.artifact.seata.transaction.SeataManualTransactionSupport;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.feign.BetaFeignClient1;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.feign.BetaFeignClient2;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.feign.BetaFeignClient3;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.feign.RawHttpRequest4;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.po.StudentPO;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds_sharding.alpha.po.TeacherPO;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Service
public class BizService {
    
    @Autowired
    BizService4Basic bizService4Basic;
    
    @Resource
    private BizShardingService shardingService;
    
    
    @Resource
    private BizService bizService;
    
    @Resource
    private BetaFeignClient1 betaFeignClient1;
    
    @Resource
    private BetaFeignClient2 betaFeignClient2;
    
    @Resource
    private BetaFeignClient3 betaFeignClient3;
    
    @Resource
    private RawHttpRequest4 betaFeignClient4;
    
    /**
     * <pre>
     *  这里是
     *
     *  【分布式事务BASE模式（seata AT实现方式）整合】 ，详见  https://gitee.com/JustryDeng/notebook/blob/master/src/main/resources/%5BJava%5Dsharding-re/%5B03%5D%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1BASE%E6%A8%A1%E5%BC%8F%EF%BC%88seata%20AT%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%EF%BC%89%E6%95%B4%E5%90%88.md
     *
     *  和
     *
     *  【dynamic-datasource整合seata】 ，详见  https://gitee.com/JustryDeng/notebook/blob/master/src/main/resources/%5BJava%5Dspring-boot-re/%5B21%5Ddynamic-datasource%E6%95%B4%E5%90%88seata.md
     *
     *  的结合使用，并无新的知识点； 了解完以上两者即可明白这里的用法了
     * </pre>
     */
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void test() {
        // 1. 本地添加Alpha数据成功
        addSomeAlphaData();
    
        // 2. feign调用beta微服务添加beta数据
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.addSomeBetaData();
        //betaFeignClient2.addSomeBetaData();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
        betaFeignClient4.addSomeBetaData();
        
        
        // 3. 本地添加alpha数据后，抛出异常 1 / 0
        addSomeAlphaData();
        System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
                                  //      注：seate有全局事务超时机制（默认50秒超时），如果超时了，那么分布式事务也会回滚，如果你要在这里断点很久，那么可以调大@GlobalTransactional的timeoutMills
                                  //         如果全局事务已经超时回滚了，那么此时再操作数据库的数据的话，那么会报错，形如：
                                  //         RmTransactionException: branch register failed, xid: 192.168.154.1:8091:72773312419283252, errMsg: TransactionException[Could not register branch into global session xid = 192.168.154.1:8091:72773312419283252 status = TimeoutRollbacking, cause by globalSession not active]
    }
    
    
    /**
     * 测试GlobalTransactional注解嵌套
     * <p>
     *     结论：可嵌套使用
     * </p>
     */
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void testNested() {
        addSomeAlphaData();
        
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.addSomeBetaData();
        //betaFeignClient2.addSomeBetaData();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
        betaFeignClient4.addSomeBetaData();
        
        /*
         * Transactional可以嵌套使用，那么GlobalTransactional是否可以签到使用呢？
         * 这里进行测试
         */
        bizService.addAlphaAndBetaData();
    
        addSomeAlphaData();
        System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
                                  //      注：seate有全局事务超时机制（默认50秒超时），如果超时了，那么分布式事务也会回滚，如果你要在这里断点很久，那么可以调大@GlobalTransactional的timeoutMills
                                  //         如果全局事务已经超时回滚了，那么此时再操作数据库的数据的话，那么会报错，形如：
                                  //         RmTransactionException: branch register failed, xid: 192.168.154.1:8091:72773312419283252, errMsg: TransactionException[Could not register branch into global session xid = 192.168.154.1:8091:72773312419283252 status = TimeoutRollbacking, cause by globalSession not active]
    }
    
    /**
     * 手动事务测试。效果等价于 {@link #test()}
     */
    @SneakyThrows
    public void testManual() {
        SeataManualTransactionSupport.create(5000).voidExec(() -> {
            addSomeAlphaData();
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.addSomeBetaData();
            //betaFeignClient2.addSomeBetaData();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
            betaFeignClient4.addSomeBetaData();
            
            int s = 6;
            while (s >= 0) {
                System.out.println("s=" + s);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                s--;
            }
    
            addSomeAlphaData();
            System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
                                      //      注：seate有全局事务超时机制（默认50秒超时），如果超时了，那么分布式事务也会回滚，如果你要在这里断点很久，那么可以调大SeataManualTransactionSupport的timeout参数
                                      //         如果全局事务已经超时回滚了，那么此时再操作数据库的数据的话，那么会报错，形如：
                                      //         RmTransactionException: branch register failed, xid: 192.168.154.1:8091:72773312419283252, errMsg: TransactionException[Could not register branch into global session xid = 192.168.154.1:8091:72773312419283252 status = TimeoutRollbacking, cause by globalSession not active]
        });
    }
    
    
    /**
     * 手动事务测试。效果等价于 {@link #testNested()}
     */
    public void testNestedManual() {
        SeataManualTransactionSupport.create().voidExec(() -> {
            addSomeAlphaData();
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.addSomeBetaData();
            //betaFeignClient2.addSomeBetaData();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
            betaFeignClient4.addSomeBetaData();
    
            addAlphaAndBetaDataManual();
            
            System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
                                      //      注：seate有全局事务超时机制（默认50秒超时），如果超时了，那么分布式事务也会回滚，如果你要在这里断点很久，那么可以调大SeataManualTransactionSupport的timeout参数
                                      //         如果全局事务已经超时回滚了，那么此时再操作数据库的数据的话，那么会报错，形如：
                                      //         RmTransactionException: branch register failed, xid: 192.168.154.1:8091:72773312419283252, errMsg: TransactionException[Could not register branch into global session xid = 192.168.154.1:8091:72773312419283252 status = TimeoutRollbacking, cause by globalSession not active]
        });
    }
    
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void addAlphaAndBetaData() {
        addSomeAlphaData();
        
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.addSomeBetaData();
        //betaFeignClient2.addSomeBetaData();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
        betaFeignClient4.addSomeBetaData();
    }
    
    /**
     * 手动事务测试。效果等价于 {@link #addAlphaAndBetaData()}
     */
    public void addAlphaAndBetaDataManual() {
        SeataManualTransactionSupport.create().voidExec(() -> {
            addSomeAlphaData();
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.addSomeBetaData();
            //betaFeignClient2.addSomeBetaData();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
            betaFeignClient4.addSomeBetaData();
            
        });
    }
    
    /**
     * 写入一些数据
     */
    public void addSomeAlphaData() {
        int id = (int)System.currentTimeMillis() % 1000000;
        // 插入数据
        bizService4Basic.insertStudent(StudentPO.builder().id(id).name("Alpha 夏洛").build()); // 数据库 mysql  db_one
    
        // 切换到指定的数据源下，执行逻辑
        DatasourceSwitcher.switchTo("oracleAlpha").voidExec(() -> {
        
            // 因为是不同的数据源，所以需要新开一个spring事务
            DefaultManualTransactionSupport.create(TransactionDefinition.PROPAGATION_REQUIRES_NEW).voidExec(
                    () -> bizService4Basic.insertTeacher(TeacherPO.builder().id(id).name("Alpha 蒋老师").build()) // 数据库 oracle  ROOT.teacher
            );
        });
        
        // 切换到指定的数据源下，执行逻辑
        DatasourceSwitcher.switchTo("shardingAlpha").voidExec(() -> {

            // 因为是不同的数据源，所以需要新开一个spring事务
            DefaultManualTransactionSupport.create(TransactionDefinition.PROPAGATION_REQUIRES_NEW).voidExec(
                    () -> shardingService.insertSomeShardingData() // 数据库 mysql  demo
            );
        });
    }
}